package com.jyw.common.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 重试工具类,待执行任务抛出异常才会进行重试
 * @author sunshine
 * @date 2022年6月14日
 */
public class RetryUtil {
	   
	   /**
	       * 自定义重试
	    * @param task 待执行任务
	    * @param time 间隔时间
	    * @param timeUnit 间隔时间单位
	    * @param retryTimes 重试次数
	    * @return T 返回值
	    * @author sunshine
	    * @date 2022年6月14日
	    */
	   public static <T> T retry(Supplier<T> task, Integer time, TimeUnit timeUnit, Integer retryTimes) throws Throwable {
		   if(time <= 0 || retryTimes <= 0 || timeUnit == null) {
			   throw new RuntimeException("无效参数");
		   }
		   List<RetryConfig> list = new ArrayList<>();
		   list.add(new RetryConfig(time,timeUnit,retryTimes));
		   return retry(task, list);
	   }
	   
	   /**
	       * 自定义不同频率重试执行
	    * @param task 待执行任务
	    * @param timeUnit 间隔时间单位
	    * @param timeList 不同频率间隔时间列表
	    * @return T 返回值
	    * @author sunshine
	    * @throws Throwable 
	    * @date 2022年6月14日
	    */
	   public static <T> T retry(Supplier<T> task, TimeUnit timeUnit,Integer... timeList) throws Throwable {
		   if(timeList == null || timeList.length <= 0 || timeUnit == null) {
			   throw new RuntimeException("无效参数");
		   }
		   List<RetryConfig> list = new ArrayList<>();
		   for(Integer time: timeList) {
			   list.add(new RetryConfig(time,timeUnit,1));  
		   }
		   return retry(task, list);
	   }
	   
		/**
		  * 自定义重试配置
		 * @param task 待执行任务
		 * @param retryConfigList 重试配置列表
		 * @return T 返回值
		 * @author sunshine
		 * @throws Throwable 
		 * @date 2022年6月14日
		 */
		private static <T> T retry(Supplier<T> task, List<RetryConfig> retryConfigList) throws Throwable {
		   int currentTimes = 1;
		   int index = 0;
		   RetryConfig scheduleConfig = retryConfigList.get(index);
		   boolean stoped = false;
		   Throwable exception = null;
		   while (!stoped) {
		      try {
		     	 T t = task.get();
		     	 stoped = true;
		         return t;
		      } catch (Throwable e) {
		     	 exception = e;
		         if (currentTimes++ >= scheduleConfig.getRetryTimes()) {
		         	currentTimes = 1;
		            scheduleConfig = retryConfigList.get(++index);
		         }
		         scheduleConfig.getTimeUnit().sleep(scheduleConfig.getTime());
		      }
		   }
		   throw exception;
		}
	   
	   private static final List<RetryConfig> defaultRetryConfigList = new ArrayList<>();
	
	   static {
	   // 间隔5秒重试,3次 
	   defaultRetryConfigList.add(new RetryConfig(5, TimeUnit.SECONDS, 3));
	   // 间隔10秒重试,3次
	   defaultRetryConfigList.add(new RetryConfig(10, TimeUnit.SECONDS, 3));
	   // 间隔30秒重试3次
	   defaultRetryConfigList.add(new RetryConfig(30, TimeUnit.SECONDS, 3));
	   //间隔30秒重试60次
	   defaultRetryConfigList.add(new RetryConfig(60, TimeUnit.SECONDS,60));
	   }
		   
		/**
		   * 默认重试计划,
		   * 每隔5秒重试一次共重试3次;
		   * 每隔10秒重试一次共重试3次;
		   * 每隔30秒重试一次共重试3次;
		   * 每隔30秒重试一次共重试3次;
		 * @param handler 待执行任务
		 * @return T 返回值
		 * @throws Throwable 
		 * @author sunshine
		 * @date 2022年6月14日
		 */
	   public static <T> T defaultRetry(Supplier<T> task) throws Throwable {
	      // 默认计划：先重试3次，再隔10s重试3次，再每分钟重试3次
	      return retry(task, defaultRetryConfigList);
	   }
	   
	   /**
	       * 定义一个抛异常的函数接口
	    * @author sunshine
	    * @date 2022年6月14日
	    */
		@FunctionalInterface
		public interface Supplier<T> {
		      T get() throws Throwable;
		   }
	}
	
	class RetryConfig {
	   //重试间隔时间
	   private Integer time;
	   //重试间隔时间单位
	   private TimeUnit timeUnit;
	   //重试次数
	   private Integer retryTimes;
	   
	   public RetryConfig(Integer time, TimeUnit timeUnit, Integer retryTimes) {
	      this.time = time;
	      this.timeUnit = timeUnit;
	      this.retryTimes = retryTimes;
	   }
	 
	public Integer getTime() {
		return time;
	}

	public void setTime(Integer time) {
		this.time = time;
	}

	public TimeUnit getTimeUnit() {
		return timeUnit;
	}

	public void setTimeUnit(TimeUnit timeUnit) {
		this.timeUnit = timeUnit;
	}

	public Integer getRetryTimes() {
		return retryTimes;
	}

	public void setRetryTimes(Integer retryTimes) {
		this.retryTimes = retryTimes;
	}
	}